#include "task.h"

int luaopen_task(lua_State * L)
{
	if(!task::h_print_mutex) task::h_print_mutex=CreateMutex(NULL,false,NULL);
	luaL_reg lib_task[]=
	{
		{"create",&task::Create},
		{NULL,NULL},
	};
	luaL_register(L,"task",lib_task);
	lua_pushcfunction(L,task::Print);
	lua_setglobal(L,"print");
	task::Reg(L);
	return 0;
}

int task::reg_key=0;
HANDLE task::h_print_mutex=0;

int task::Create(lua_State * L)
{
	task * ud=task::Push(L);
	ud->sts=TASK_STS_FINISHED;
	ud->L=lua_open();
	luaL_openlibs(ud->L);
	lua_pushcfunction(ud->L,&task::Suspend);
	lua_setfield(ud->L,LUA_GLOBALSINDEX,"suspend");
	lua_pushlightuserdata(ud->L,(void *)&task::reg_key);
	lua_pushlightuserdata(ud->L,(void *)ud);
	lua_settable(ud->L,LUA_REGISTRYINDEX);
	lua_pushcfunction(ud->L,task::Print);
	lua_setglobal(ud->L,"print");
	return 1;
}

int task::DoString(lua_State * L)
{
	task * ud=(task *)luaL_checkudata(L,1,TYPENAME_TASK);
	if(ud->sts==TASK_STS_RUNNING) return luaL_error(L,"Task is running.");
	ud->sts=TASK_STS_RUNNING;
	const char * code=luaL_checkstring(L,2);
	if(luaL_loadstring(ud->L,code))
	{
		strcpy_s(ud->errmsg,ERR_MSG_LEN,lua_tostring(ud->L,-1));
		ud->sts=TASK_STS_ERROR;
		return 1;
	}
	ud->h_thread=CreateThread(NULL,0,task::ThreadProc,ud,CREATE_SUSPENDED,NULL);
	ud->h_mutex=CreateMutex(NULL,false,NULL);
	ResumeThread(ud->h_thread);
	Sleep(1);
	return 0;
}

int task::Print(lua_State * L)
{
  WaitForSingleObject(task::h_print_mutex,INFINITE);
  //The following is copied from Lua source code lbaselib.c
  int n = lua_gettop(L);  /* number of arguments */
  int i;
  lua_getglobal(L, "tostring");
  for (i=1; i<=n; i++) {
    const char *s;
    lua_pushvalue(L, -1);  /* function to be called */
    lua_pushvalue(L, i);   /* value to print */
    lua_call(L, 1, 1);
    s = lua_tostring(L, -1);  /* get result */
    if (s == NULL)
      return luaL_error(L, LUA_QL("tostring") " must return a string to "
                           LUA_QL("print"));
    if (i>1) fputs("\t", stdout);
    fputs(s, stdout);
    lua_pop(L, 1);  /* pop result */
  }
  fputs("\n", stdout);
  //
  ReleaseMutex(task::h_print_mutex);
  return 0;
}

void task::Reg(lua_State * L)
{
	luaL_Reg methods[]=
	{
		{"get",&task::Get},
		{"set",&task::Set},
		{"dostring",&task::DoString},
		{"status",&task::Status},
		{NULL,NULL},
	};
	luaL_Reg metamethods[]=
	{
		{"__tostring",task::ToString},
		{NULL,NULL},
	};
	luaL_openlib(L,TYPENAME_TASK,methods,0);
	luaL_newmetatable(L,TYPENAME_TASK);
	luaL_openlib(L,0,metamethods,0);
	lua_pushliteral(L,"__index");
	lua_pushvalue(L,-3);
	lua_rawset(L,-3);
	lua_pushliteral(L,"__metatable");
	lua_pushvalue(L,-3);
	lua_rawset(L,-3);
	lua_pop(L,2);
}

task * task::Push(lua_State * L)
{
	task * ud=(task *)lua_newuserdata(L,sizeof(task));
	luaL_getmetatable(L,TYPENAME_TASK);
	lua_setmetatable(L,-2);
	return ud;
}

int task::ToString(lua_State * L)
{
	static char buffer[256];
	task * ud=(task *)luaL_checkudata(L,1,TYPENAME_TASK);
	sprintf_s<256>(buffer,"task: %08X",ud);
	lua_pushstring(L,buffer);
	return 1;
}

int task::Get(lua_State * L)
{
	task * ud=(task *)luaL_checkudata(L,1,TYPENAME_TASK);
	const char * field=luaL_checkstring(L,2);
	WaitForSingleObject(ud->h_mutex,INFINITE);
	lua_getglobal(ud->L,field);
	switch(lua_type(ud->L,-1))
	{
	case LUA_TNUMBER:
		lua_pushnumber(L,lua_tonumber(ud->L,-1));
		break;
	case LUA_TSTRING:
		lua_pushstring(L,lua_tostring(ud->L,-1));
		break;
	case LUA_TBOOLEAN:
		lua_pushboolean(L,lua_toboolean(ud->L,-1));
		break;
	case LUA_TLIGHTUSERDATA:
		lua_pushlightuserdata(L,lua_touserdata(ud->L,-1));
		break;
	default:
		lua_pushnil(L);
		break;
	}
	lua_pop(ud->L,1);
	ReleaseMutex(ud->h_mutex);
	return 1;
}

int task::Set(lua_State * L)
{
	task * ud=(task *)luaL_checkudata(L,1,TYPENAME_TASK);
	const char * field=luaL_checkstring(L,2);
	WaitForSingleObject(ud->h_mutex,INFINITE);
	switch(lua_type(L,3))
	{
	case LUA_TNUMBER:
		lua_pushnumber(ud->L,lua_tonumber(L,3));
		break;
	case LUA_TSTRING:
		lua_pushstring(ud->L,lua_tostring(L,3));
		break;
	case LUA_TBOOLEAN:
		lua_pushboolean(ud->L,lua_toboolean(L,3));
		break;
	case LUA_TLIGHTUSERDATA:
		lua_pushlightuserdata(ud->L,lua_touserdata(L,3));
		break;
	default:
		lua_pushnil(ud->L);
		break;
	}
	lua_setglobal(ud->L,field);
	ReleaseMutex(ud->h_mutex);
	return 0;
}

int task::Status(lua_State * L)
{
	task * ud=(task *)luaL_checkudata(L,1,TYPENAME_TASK);
	switch(ud->sts)
	{
	case TASK_STS_RUNNING:
		lua_pushstring(L,"running");
		return 1;
		break;
	case TASK_STS_FINISHED:
		lua_pushstring(L,"finished");
		return 1;
		break;
	case TASK_STS_ERROR:
		lua_pushstring(L,"error");
		lua_pushstring(L,ud->errmsg);
		return 2;
		break;
	default:
		break;
	}
	return 0;
}

DWORD WINAPI task::ThreadProc(LPVOID lpParam)
{
	task * ud=(task *)lpParam;
	int status;
	WaitForSingleObject(ud->h_mutex,INFINITE);
	status=lua_pcall(ud->L,0,LUA_MULTRET,0);
	if(status)
	{
		strcpy_s(ud->errmsg,ERR_MSG_LEN,lua_tostring(ud->L,-1));
		ud->sts=TASK_STS_ERROR;
		return NULL;
	}
	else
	{
		ud->sts=TASK_STS_FINISHED;
		return NULL;
	}
	ReleaseMutex(ud->h_mutex);
	return NULL;
}

int task::Suspend(lua_State * L)
{
	lua_pushlightuserdata(L,(void *)&task::reg_key);
	lua_gettable(L,LUA_REGISTRYINDEX);
	task * ud=(task *)lua_touserdata(L,-1);
	lua_pop(L,1);
	ReleaseMutex(ud->h_mutex);
	WaitForSingleObject(ud->h_mutex,INFINITE);
	return 0;
}